﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using System.Windows.Forms;
using Kawanon.Foundation;
using Kawanon.QtHoney;
using QTPlugin.Interop;
using QTPlugin;
using System.Runtime.InteropServices;

namespace Kawanon.BandExtension
{
	[Serializable]
	public class AddressBarInformation
	{
		[Category("設定のオン/オフ")]
		[DisplayName("アドレスバーを改造する")]
		[Description("アドレスバーの設定を有効にします。標準アドレスバーまたは QTAddressBar に対する改造を行います。")]
		[TypeConverter(typeof(YesNoConverter))]
		[DefaultValue(false)]
		public bool Enabled { get; set; }

		[Category("動作")]
		[DisplayName("通常")]
		[Description("パスを入力して Enter キーまたは移動ボタンの押下、ドロップダウンの選択、"+
			"そして QTAddressBar のパンくずクリックの動作を指定します。" +
			"ただし、Vista の標準アドレスバーのパンくずクリックには対応していません。")]
		[DefaultValue(AddressBars.Target.Default)]
		public AddressBars.Target Normal { get; set; }

		[Category("動作")]
		[DisplayName("Shift キー")]
		[Description("標準アドレスバーでの Shift キー押下時 の動作を指定します。"+
			"QTAddressBar では常に新規タブを開くため、利用できません。")]
		[DefaultValue(AddressBars.Target.NewTab)]
		public AddressBars.Target ShiftPressed { get; set; }
	
		//NOTE: 標準アドレスバーでは「URLを開く」になってしまう...
		//public AddressBars.Target ControlPressed { get; set; }
		//public AddressBars.Target ControlShiftPressed { get; set; }

		public AddressBars.Target GetTarget()
		{
			switch (Control.ModifierKeys)
			{
				//case Keys.Control:
				//    return ControlPressed;

				//case Keys.Control | Keys.Shift:
				//    return ControlShiftPressed;

				case Keys.Shift:
					return ShiftPressed;
			}

			return Normal;
		}

		public AddressBarInformation()
		{
			Enabled = false;
			Normal = AddressBars.Target.Default;
			ShiftPressed = AddressBars.Target.NewTab;
		}
	}

	public class NAddressBarComponent : NativeWindow
	{
		public object TabBar { get; set; }

		protected void Navigate(ref Message m)
		{
			try
			{
				NavigateContext context = new NavigateContext(TabBar);
				AddressBars.BeginNavigate(context);
				base.WndProc(ref m);
				AddressBars.EndNavigate(context);
			}
			catch (Exception ex)
			{
				Dbg.Echo(ex);
			}
		}

	}

	public class NAddressBarComboBoxEx : NAddressBarComponent
	{
		private const uint WM_NOTIFY = 0x004E;
		private const uint WM_COMMAND = 0x0111;

		private const uint CBN_SELENDOK = 9;

		//private const int CBEN_ENDEDITW = -806;

		//[StructLayout(LayoutKind.Sequential)]
		//struct NMHDR
		//{
		//    public IntPtr hwndFrom;
		//    public IntPtr idFrom;
		//    public int code;
		//}

		protected override void WndProc(ref Message m)
		{
			switch ((uint)m.Msg)
			{
				//case WM_NOTIFY:
				//    NMHDR nm = (NMHDR)m.GetLParam(typeof(NMHDR));
				//    if (nm.code == CBEN_ENDEDITW)
				//    {
				//        Dbg.Play();
				//        return;
				//    }
				//    break;

				case WM_COMMAND:
					if (Utility.HighWord(m.WParam) == CBN_SELENDOK)
					{
						Navigate(ref m);
						return;
					}
					break;
			}
			base.WndProc(ref m);
		}
	}

	public class NAddressBarTextBox : NAddressBarComponent
	{
		private const uint WM_KEYDOWN = 0x0100;
		private const uint WM_CHAR = 0x0102;

		protected override void WndProc(ref Message m)
		{
			//switch ((uint)m.Msg)
			//{
			//    case WM_KEYDOWN:
			//        Keys key = (Keys)m.WParam;
			//        if (key == Keys.Enter)
			//        {
			//        //	Dbg.Play();
			//            Utility.Protect(() => AddressBars.CloneTab(TabBar));
			//            //Dbg.Echo("");

			//            //return;
			//        }
			//        break;
			//}

			switch ((uint)m.Msg)
			{
				case WM_KEYDOWN:
					if ((Keys)m.WParam == Keys.Enter)
					{
						Navigate(ref m);
						return;
					}
					break;
			}
			base.WndProc(ref m);
		}
	}

	internal class ShellBrowserProxy : RealProxy
	{
		public IShellBrowser Browser { get; set; }
		public object TabBar { get; set; }
		public object BandObject { get; set; }

		public ShellBrowserProxy(Type t)
			: base(t)
		{
		}

		public override IMessage Invoke(IMessage msg)
		{
			IMethodCallMessage req = msg as IMethodCallMessage;
			if (req != null)
			{
				if (req.MethodName == "BrowseObject")
				{
					try
					{
						NavigateContext context = new NavigateContext(TabBar);
						AddressBars.BeginNavigate(context);
						IMessage ret = RemotingServices.ExecuteMessage((MarshalByRefObject)Browser, req);
						AddressBars.EndNavigate(context);
						return ret;
					}
					catch (Exception ex)
					{
						Dbg.Echo(ex);
					}

					//ReturnMessage ret = new ReturnMessage(0, null, 0, req.LogicalCallContext, req);
					//return ret;
				}
			}

			return RemotingServices.ExecuteMessage((MarshalByRefObject)Browser, req);
		}
	}

	public class NavigateContext
	{
		public object PreviousTab;
		public AddressBarInformation Information { get; private set; }
		public object TabBar { get; private set; }

		public NavigateContext(object tabBar)
		{
			Information = Persistence.GetInformation<AddressBarInformation>();
			TabBar = tabBar;
		}
	}

	public class AddressBars
	{
		private const uint S_OK = 0;

		[TypeConverter(typeof(EnumDisplayNameConverter))]
		public enum Target
		{
			[EnumDisplayName("現在のタブ")]
			Default,

			[EnumDisplayName("新しいタブ")]
			NewTab,

			[EnumDisplayName("新しいウィンドウ")]
			NewWindow,
		}

		private static Dictionary<object, List<object>> s_hooks = new Dictionary<object, List<object>>();

		public static void BeginNavigate(NavigateContext context)
		{
			object tabBar = context.TabBar;

			switch (context.Information.GetTarget())
			{
				case Target.NewTab:
				case Target.NewWindow:
					Control c = Utility.GetTabControl(tabBar);
					Utility.SetRedraw(c.Handle, false);

					CloneTab(tabBar, context);
					break;
			}
		}

		public static void EndNavigate(NavigateContext context)
		{
			object tabBar = context.TabBar;
			Target target = context.Information.GetTarget();
			switch (target)
			{
				case Target.NewWindow:
					IPluginServer server = Utility.GetPluginServer(tabBar);
					ITab tab = server.SelectedTab;
					server.CreateWindow(tab.Address);

					object tabControl = Utility.GetTabControl(tabBar);
					SelectTab(tabControl, context.PreviousTab);

					tab.Close();
					break;
			}

			if (target != Target.Default)
			{
				Control c = Utility.GetTabControl(tabBar);
				if (Utility.IsVista())
				{
					Utility.SetRedraw(c.Handle, true);
					c.Invalidate();
				}
				else
				{
					c.Invalidate();	// 再描画前にタブを更新
					Utility.SetRedraw(c.Handle, true);
				}

			}
		}

		private static void SelectTab(object tabControl, object tab)
		{
			MethodInfo method = tabControl.GetType().GetMethod("SelectTab", new Type[] { tab.GetType().BaseType });
			//Dbg.Echo(method);
			method.Invoke(tabControl, new object[] { tab });
		}

		// 新規タブを用意する (Target.NewTab)
		private static void CloneTab(object tabBar, NavigateContext context)
		{
			object tabControl = Utility.GetTabControl(tabBar);
			object tabPages = Utility.GetPropertyValue(tabControl, "TabPages");
			object tab = Utility.GetFieldValue(tabBar, "CurrentTab");
			if (context != null)
			{
				context.PreviousTab = tab;
			}

			// CloneTabButton は NowTabCloned でカチリ音がするので使わない
			//{
			//    MethodInfo method = TabBar.GetType().GetMethod("CloneTabButton",
			//        BindingFlags.Instance | BindingFlags.NonPublic,
			//        null,
			//        new Type[] { tab.GetType(), typeof(string), typeof(bool), typeof(int) },
			//        null);
			//    method.Invoke(TabBar, new object[] { tab, null, true, -1 });
			//}


			{
				MethodInfo method = tab.GetType().GetMethod("Clone", Type.EmptyTypes);
				//Dbg.Echo(method);
				tab = method.Invoke(tab, null);
			}
			{
				MethodInfo method = tabBar.GetType().GetMethod("AddInsertTab",
					BindingFlags.Instance | BindingFlags.NonPublic);
				//Dbg.Echo(method);
				method.Invoke(tabBar, new object[] { tab });
			}

			SelectTab(tabControl, tab);
		}

		private static IEnumerable<IntPtr> EnumAddressBars(IPluginServer server)
		{
			object tabBar = Utility.GetBandObject(server);
			object inputSite = Utility.GetFieldValue(tabBar, "BandObjectSite");
			IBandSite bandSite = inputSite as IBandSite;

			if (Utility.IsVista())
			{
				//IPluginServer server = Utility.GetPluginServer(tabBar);
				IntPtr hBandRoot = Utility.FindChildWindow(server.ExplorerHandle,
					Utility.EqualsClassName("Address Band Root"));
				if (hBandRoot != IntPtr.Zero)
				{
					IntPtr hCombo = Utility.FindChildWindow(hBandRoot,
						Utility.EqualsClassName("ComboBoxEx32"));
					yield return hCombo;
				}
			}

			// EnumBands で-1を与えるにはnullポインタが必要なので無理
			for (int i = 0; ; i++)
			{
				uint id;
				int hr = bandSite.EnumBands(i, out id);
				if (hr != S_OK)
				{
					//Dbg.Echo(hr);
					break;
				}
				//Dbg.Echo(id);

				Guid IID_IUnknown = new Guid("{00000000-0000-0000-C000-000000000046}");
				object ppv;
				bandSite.GetBandObject(id, ref IID_IUnknown, out ppv);
				//Dbg.Echo(ppv);

				if (id == 4)	// アドレスバーの場合
				{
					IOleWindow win = (IOleWindow)ppv;
					IntPtr hwnd;
					win.GetWindow(out hwnd);
					yield return hwnd;
				}
				else if (ppv.GetType().FullName == "QTBreadcrumbs.Breadcrumbs")
				{
					Control c = (Control)ppv;
					yield return c.Handle;
				}

				//Guid clsid = new Guid();
				//IPersist persist = ppv as IPersist;
				//if (persist != null)
				//{
				//    persist.GetClassID(out clsid);
				//}
				//Dbg.Echo(id);
				//Dbg.Echo(clsid);
			}
		}

		private static object HookAddressBar(object tabBar, IntPtr hwndBand)
		{
			//c.PreviewKeyDown += new PreviewKeyDownEventHandler(Control_PreviewKeyDown);
			//Dbg.Echo(c);

			Control band = Control.FromHandle(hwndBand);
			IShellBrowser browser = (IShellBrowser)Utility.GetFieldValue(band, "ShellBrowser");
			//Dbg.Echo(band);

			Type t = band.GetType().Assembly.GetType("QTBreadcrumbs.IShellBrowser");
			//Dbg.Echo(t);
			ShellBrowserProxy proxy = new ShellBrowserProxy(t);
			proxy.Browser = browser;
			proxy.TabBar = tabBar;
			proxy.BandObject = band;

			object wrapper = proxy.GetTransparentProxy();
			Utility.SetFieldValue(band, "ShellBrowser", wrapper);

			return proxy;
		}

		private static object BindAddressBarComponent(object tabBar, IntPtr hwnd, NAddressBarComponent bar)
		{
			if (hwnd == IntPtr.Zero)
			{
				Dbg.Echo("HookAddressBar window is null");
				throw new WindowNotFoundException();
			}
			if (NativeWindow.FromHandle(hwnd) != null)
			{
				throw new AlreadyAttachedException();
			}

			bar.TabBar = tabBar;
			// とりあえず ControlNativeWindow に対して重複してセットしても問題なさげ？
			//NOTE: PreviewKeyDown にイベントをセットする のほうが良いかも
			bar.AssignHandle(hwnd);

			//NOTE: GCに回収されないようにする
			return bar;
		}

		private static void RegisterHookObject(object band, object bar)
		{
			List<object> bars = null;
			if (!s_hooks.TryGetValue(band, out bars))
			{
				bars = new List<object>();
				s_hooks.Add(band, bars);
			}
			bars.Add(bar);
		}

		public static void Hook(IPluginServer server)
		{
			object band = Utility.GetBandObject(server);

			foreach (IntPtr hwndBand in EnumAddressBars(server))
			{
				//NOTE: QTAddressBar の場合
				if (Control.FromHandle(hwndBand) != null)
				{
					//IntPtr hwnd = Utility.FindChildWindow(h, delegate(IntPtr child)
					//{
					//    Control c = Control.FromHandle(child);
					//    if (c != null)
					//    {
					//        return c is TextBox;
					//    }
					//    else
					//    {
					//        throw new Exception("Window isnot Control");
					//    }
					//});
					RegisterHookObject(band, HookAddressBar(band, hwndBand));
				}
				else
				{
					IntPtr hwndEdit = Utility.FindChildWindow(hwndBand, Utility.EqualsClassName("Edit"));

					RegisterHookObject(band, BindAddressBarComponent(band, hwndBand, new NAddressBarComboBoxEx()));
					RegisterHookObject(band, BindAddressBarComponent(band, hwndEdit, new NAddressBarTextBox()));
				}

				//	Dbg.Echo(hwnd.ToInt32().ToString("x"));

			}
			
		}

		public static void Unhook(IPluginServer server)
		{
			object band = Utility.GetBandObject(server);

			List<object> bars = null;
			if (!s_hooks.TryGetValue(band, out bars))
			{
				return;
			}

			foreach (object obj in bars)
			{
				ShellBrowserProxy proxy = obj as ShellBrowserProxy;
				if (proxy != null)
				{
					Utility.SetFieldValue(proxy.BandObject, "ShellBrowser", proxy.Browser);
					continue;
				}
				NAddressBarComponent bar = obj as NAddressBarComponent;
				if (bar != null)
				{
					bar.ReleaseHandle();
					continue;
				}

				Dbg.Abort("Unknown AddressBar Object: "+ obj);
			}
		}

		//private static void Control_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
		//{
		//    Dbg.Echo(s_hooks.Count);

		//    foreach (KeyValuePair<object, List<object>> pair in s_hooks)
		//    {
		//        int index = pair.Value.IndexOf(sender);
		//        if (index == -1)
		//        {
		//            continue;
		//        }

		//        try
		//        {
		//            CloneTab(pair.Key);
		//        }
		//        catch (Exception ex)
		//        {
		//            Dbg.Echo(ex);
		//        }
		//    }
		//}
	}
}